using System.Reflection.Emit;

namespace MagicOnion.Internal.Reflection;

// full list of can create optimize helper -> https://github.com/kevin-montrose/Sigil#automated-opcode-choice

/// <summary>
/// Provides optimized generation code and helpers.
/// </summary>
internal static class ILGeneratorExtensions
{
    /// <summary>
    /// Loads the local variable at a specific index onto the evaluation stack.
    /// </summary>
    public static void EmitLdloc(this ILGenerator il, int index)
    {
        switch (index)
        {
            case 0:
                il.Emit(OpCodes.Ldloc_0);
                break;
            case 1:
                il.Emit(OpCodes.Ldloc_1);
                break;
            case 2:
                il.Emit(OpCodes.Ldloc_2);
                break;
            case 3:
                il.Emit(OpCodes.Ldloc_3);
                break;
            default:
                if (index <= 255)
                {
                    il.Emit(OpCodes.Ldloc_S, (byte)index);
                }
                else
                {
                    il.Emit(OpCodes.Ldloc, (short)index);
                }
                break;
        }
    }

    /// <summary>
    /// Pops the current value from the top of the evaluation stack and stores it in a the local variable list at a specified index.
    /// </summary>
    public static void EmitStloc(this ILGenerator il, int index)
    {
        switch (index)
        {
            case 0:
                il.Emit(OpCodes.Stloc_0);
                break;
            case 1:
                il.Emit(OpCodes.Stloc_1);
                break;
            case 2:
                il.Emit(OpCodes.Stloc_2);
                break;
            case 3:
                il.Emit(OpCodes.Stloc_3);
                break;
            default:
                if (index <= 255)
                {
                    il.Emit(OpCodes.Stloc_S, (byte)index);
                }
                else
                {
                    il.Emit(OpCodes.Stloc, (short)index);
                }
                break;
        }
    }

    /// <summary>
    /// Loads the address of the local variable at a specific index onto the evaluation statck.
    /// </summary>
    public static void EmitLdloca(this ILGenerator il, int index)
    {
        if (index <= 255)
        {
            il.Emit(OpCodes.Ldloca_S, (byte)index);
        }
        else
        {
            il.Emit(OpCodes.Ldloca, (short)index);
        }
    }

    /// <summary>
    /// Pushes a supplied value of type int32 onto the evaluation stack as an int32.
    /// </summary>
    public static void EmitLdc_I4(this ILGenerator il, int value)
    {
        switch (value)
        {
            case -1:
                il.Emit(OpCodes.Ldc_I4_M1);
                break;
            case 0:
                il.Emit(OpCodes.Ldc_I4_0);
                break;
            case 1:
                il.Emit(OpCodes.Ldc_I4_1);
                break;
            case 2:
                il.Emit(OpCodes.Ldc_I4_2);
                break;
            case 3:
                il.Emit(OpCodes.Ldc_I4_3);
                break;
            case 4:
                il.Emit(OpCodes.Ldc_I4_4);
                break;
            case 5:
                il.Emit(OpCodes.Ldc_I4_5);
                break;
            case 6:
                il.Emit(OpCodes.Ldc_I4_6);
                break;
            case 7:
                il.Emit(OpCodes.Ldc_I4_7);
                break;
            case 8:
                il.Emit(OpCodes.Ldc_I4_8);
                break;
            default:
                if (value >= -128 && value <= 127)
                {
                    il.Emit(OpCodes.Ldc_I4_S, (sbyte)value);
                }
                else
                {
                    il.Emit(OpCodes.Ldc_I4, value);
                }
                break;
        }
    }

    /// <summary>
    /// Helper for Pop op.
    /// </summary>
    public static void EmitPop(this ILGenerator il, int count)
    {
        for (int i = 0; i < count; i++)
        {
            il.Emit(OpCodes.Pop);
        }
    }

    public static void EmitNullReturn(this ILGenerator il)
    {
        il.Emit(OpCodes.Ldnull);
        il.Emit(OpCodes.Ret);
    }

    public static void EmitThrowNotimplemented(this ILGenerator il)
    {
        il.Emit(OpCodes.Newobj, typeof(NotImplementedException).GetConstructor(Type.EmptyTypes)!);
        il.Emit(OpCodes.Throw);
    }
}
